using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;

namespace InfiniTec.Security
{
    /// <summary>
    /// This class assists the use of <see cref="SecureString"/> instances.
    /// </summary>
    /// <remarks>
    /// Normally, the content of a <see cref="SecureString"/> instance can only be read in unmnanaged code.
    /// This class decrypts the contents of a SecureString to a <see cref="byte"/> array, which
    /// is pinned in memory until it is released. When the dispose method is called, the
    /// array is zeroed-out.
    /// </remarks>
    /// <example>
    /// You should use this class like in this example, and only for the shortest period of time
    /// necessary:
    /// <code>
    /// Rijndael aes = Rijndael.Create();
    /// DeriveBytes db;
    /// using (SecureStringDecryptor helper = new SecureStringDecryptor(secretString)) {
    ///     db = new Rfc2898DeriveBytes(helper.Password, salt, 1024);
    /// }
    /// ICryptoTransform transform = aes.CreateEncryptor(db.GetBytes(16), aes.GenerateIV());
    /// </code>
    /// </example>
    public class SecureStringDecryptor: CriticalFinalizerObject, IDisposable
    {
        private byte[] _Password;
        GCHandle? bhandle;


        /// <summary>
        /// Returns the password that is stored in the <see cref="SecureString"/> object that was
        /// passed to the constructor of this class.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public byte[] Password
        {
            get { return _Password; }
        }

        /// <summary>
        /// Initializes a new instance of this class and decrypts the content of the <see cref="SecureString"/> object
        /// into the <see cref="Password"/> array.
        /// </summary>
        /// <param name="secret"></param>
        public SecureStringDecryptor(SecureString secret)
        {
            IntPtr handle;

            bhandle = null;
            handle = IntPtr.Zero;

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                handle = Marshal.SecureStringToGlobalAllocUnicode(secret);

                _Password = new byte[secret.Length*sizeof(char)];
                bhandle = GCHandle.Alloc(_Password, GCHandleType.Pinned);

                for (int i = 0; i < _Password.Length; i++)
                {
                    _Password[i] = Marshal.ReadByte(handle, i);
                }
            }
            finally
            {
                if (handle != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(handle);
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Calls the <see cref="Dispose(bool)"/> method.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Zeroes out and unpins the <see cref="Password" /> array.
        /// </summary>
        /// <param name="disposing">True, if called from the <see cref="Dispose()"/> methods, false if called from the
        /// destructor.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_Password != null) Array.Clear(_Password, 0, _Password.Length);
                if (bhandle.HasValue) bhandle.Value.Free();
            }
        }

        /// <summary>
        /// Zeroes out and unpins the <see cref="Password" /> array.
        /// </summary>
        ~SecureStringDecryptor()
        {
            Dispose(false);
        }
        #endregion
    }
}
